iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
生成式 AI

打造基於 MCP 協議與 n8n 工作流的會議處理 Agent系列 第 19

Day 19 整合 Gmail — 自動化任務指派通知

  • 分享至 

  • xImage
  •  

經過前面幾天對 M2A Agent 任務鏈的強化,我的 AI 助理現在已經能夠產出相當水準的會議摘要與行動任務了。但是現在的所有結果都只靜靜地躺在 Notion 裡,如果沒有人主動去看,被指派任務的夥伴可能不知道有新任務。

我為了讓 M2A Agent 能夠朝成為一個主動、貼心的助理邁進,因此今天的目標是整合 Gmail,讓系統在指派任務的當下,自動寄出一封通知信給相關成員。

今天的目標與挑戰

  • 在 Google Cloud Console 完成 Gmail API 的憑證設定
  • 學習如何在 n8n 中設定 Google 的 OAuth2 憑證
  • 設計一封內容完整、格式精美的 HTML 格式任務通知郵件
  • 更新現有的工作流程,串接「Gmail」節點

Step 1:設定 Gmail API 憑證

要讓 n8n 有權限透過自己的 Gmail 帳號寄信,我們必須先到 Google Cloud Console 進行設定,取得授權憑證,這個過程可以想像成要去申請一張「代理寄信許可證」。

1-1 建立專案並啟用 API

第一步,我們要在 Google 的雲端平台建立一個專案來管理我們的 API。

  1. 前往 Google Cloud Console

  2. 點擊左上角的選取專案,選擇「新增專案」。
    Create New Project 1

  3. 為專案取一個好記的名稱,例如 M2A Agent Notifications,然後點擊「建立」。
    Create New Project 2
    Create New Project 3(通知)

  4. 專案建立後,從左側導覽列選擇「API 和服務」下的「程式庫」。
    Create New Project 4

  5. 在搜尋框中輸入 Gmail API,點擊進入後,按下「啟用」按鈕。
    Create New Project 5 (gmail API)
    Create New Project 6 (gmail API)
    Create New Project 7 (gmail API)

  6. 最後選擇我們剛剛建立的專案
    Create New Project 8 (gmail API)
    Create New Project 9 (gmail API) Successful
    即可完成

1-2 設定 OAuth 同意畫面

A. 建立基本品牌資訊

這是設定 OAuth 同意畫面的第一步,主要是建立應用程式的基本樣貌。

  1. 在左側導覽列選擇「API 和服務」下的「OAuth 同意畫面」。

OAuth 1
2. 在「OAuth 同意畫面」頁面後,點擊「開始」進入設定精靈。
OAuth 2
3. 應用程式資訊
* 應用程式名稱M2A Agent 任務通知器
* 使用者支援電子郵件:選擇自己的 Gmail 信箱
OAuth Project Settings 1
4. 目標對象
* 選擇外部
OAuth Project Settings 2
5. 聯絡資訊
* 開發人員聯絡資訊:再次輸入自己的 Gmail 信箱
OAuth Project Settings 3
6. 點擊「儲存並繼續」,完成最初的品牌建立。
OAuth Project Settings 4

B. 新增應用程式範圍

完成基本設定後,我們要明確告訴 Google,這個應用程式只需要「寄送郵件」的權限。

  1. 從左側導覽列選擇「資料存取權」,點擊「新增或移除範圍」。
    1
  2. 在右側彈出的視窗中,篩選要 https://www.googleapis.com/auth/gmail.send 這個範圍,勾選它,然後點擊「更新」。
    2
  3. 回到「資料存取權」頁面後,點擊最下方的「Save」按鈕來儲存變更,這樣就成功將權限加入了。
    3

C. 新增測試使用者

在我們的應用程式通過 Google 官方驗證之前,只有被加入這個清單的測試使用者才能授權 n8n 存取 Gmail。

  1. 從左側導覽列選擇「目標對象」,在「測試使用者」區塊,點擊「+ Add users」。
    1
  2. 在跳出的新增使用者視窗中,輸入你自己的 Gmail 信箱。
  3. 點擊右下角的「儲存」按鈕,將自己加入測試名單。
    2

1-3 建立 OAuth 2.0 憑證

最後關鍵的一步,就是要取得 n8n 需要的「用戶端 ID」和「用戶端密鑰」。

A. 在 n8n 中找到專屬重新導向 URI

在跟 Google 申請金鑰前,我們得先告訴 Google,當授權成功後,要將使用者導向回 n8n 的哪個地址。

  1. 打開你的 n8n 操作介面。
  2. 點擊左上角的 + 號,並選擇「Credentials」。
  3. 在搜尋框輸入 Gmail OAuth2 API 並點選它。
    Gmail OAuth2 API Credential
  4. 在跳出的設定視窗中,找到「OAuth Redirect URL」欄位有一串專屬的網址
  5. 點擊它旁邊的複製按鈕,將這串完整的網址複製下來。
    OAuth Redirect URL

B. 在 Google Cloud Console 註冊 URI 並建立金鑰

現在拿著 n8n 給我們的網址,回到 Google Cloud 進行註冊。

  1. 回到 Google Cloud Console,點擊左側導覽列選擇「API 和服務」下的「憑證」。
    1

  2. 在「憑證」頁面,點擊上方的「+ 建立憑證」,然後在下拉選單中選擇「OAuth 用戶端 ID」。
    2

  3. 應用程式類型:選擇「網頁應用程式」。
    3

  4. 已授權的重新導向 URI:點擊「+ 新增 URI」,然後將剛剛從 n8n 複製的專屬網址完整地貼上。

  5. 點擊頁面最下方的「建立」。
    4

  6. 建立後就會有一個彈出視窗會顯示專屬的「用戶端 ID」和「用戶端密碼」。請將這兩串文字複製下來,並且妥善的保管好。
    5

C. 回到 n8n 完成最終設定

最後我們要將 Google 給的鑰匙和權限範圍,都設定回 n8n。

  1. 回到 n8n 的憑證設定視窗。
  2. 將從 Google 取得的「用戶端 ID」和「用戶端密碼」,分別貼入 n8n 頁面中對應的 Client IDClient Secret 欄位。
  3. 在下面的「Scope」欄位,將以下代表「寄送郵件權限」的網址完整地貼進去https://www.googleapis.com/auth/gmail.send
  4. 確認 Client IDClient SecretScope 三個欄位都已正確填寫。
  5. 點擊「Sign in with Google」按鈕,並用 Google 帳號登入授權。
    Sign with Google
  6. 頁面跳回 n8n,並顯示綠色的「Account connected」訊息,就代表成功了!
    Account connected Successful

Step 2:改造 n8n 工作流

為了處理多位指派者的情況,因此要採用「先分組,再迴圈」的策略。我們需要先將所有任務「按人分組」,然後一個一個地處理每個人,為他們各自寄送郵件。

2-1 新增「任務分組」節點

這個節點的的任務是把零散的任務,然後將它們重新打包,變成以「人」為單位的包裹。

  1. 在 n8n 工作流中,在「提取結構化任務」節點之後,新增一個「Code」節點。
  2. 將它重新命名為「任務分組」。
  3. 程式碼區塊中,撰寫以下程式碼
// 從上游節點取得任務清單和成員資料
const enhancedTasks = $('提取結構化任務').first().json.task_info.tasks;
const memberData = $('格式化成員列表').first().json.member_data;

// 建立一個物件來存放按指派者分組的任務
const tasksByAssignee = {};

// 遍歷所有任務
for (const task of enhancedTasks) {
  // 只處理有明確指派者的任務
  if (task.assigned_to_id) {
    const assigneeId = task.assigned_to_id;

    // 如果這是第一次看到這位指派者,先為他建立一個基本資料結構
    if (!tasksByAssignee[assigneeId]) {
      const member = memberData.find(m => m.notion_user_id === assigneeId);
      if (member && member.email) { // 確保成員存在且有 Email
        tasksByAssignee[assigneeId] = {
          name: member.name,
          email: member.email,
          tasks: [] // 一個用來存放他所有任務的陣列
        };
      }
    }
    
    // 將當前任務加入到對應指派者的任務清單中
    if (tasksByAssignee[assigneeId]) {
      tasksByAssignee[assigneeId].tasks.push(task);
    }
  }
}

// 最後,將物件轉換成陣列格式,方便下一個節點處理
// Object.values() 會將 { 'id1': { ... }, 'id2': { ... } } 轉換成 [ { ... }, { ... } ]
return Object.values(tasksByAssignee);

這個節點的輸出,會是一個陣列,陣列中的每個項目都代表一位被指派者,以及他需要完成的所有任務清單。

2-2 新增「Loop Over Items」節點

現在我們有了一個包含「張三包裹」和「李四包裹」的籃子,但後面的 Gmail 節點一次只能寄一封信,這時候「Loop Over Items」節點就派上用場了。它的任務是分發,也就是建立一個迴圈,把籃子裡的包裹一個一個拿出來,交給下一個節點處理。

  1. 在「任務分組」節點之後,新增一個名為「Loop Over Items」的節點。
    2.在節點的設定中,將「Batch Size」設為 1
  2. 這個設定的意思是「一次只處理一個批次,每個批次包含 1 個項目」。因為上一個節點的的輸出是一個陣列,這個設定會讓 n8n 將陣列中的每一個項目(也就是每一個人的資料包裹)單獨地、依序地執行後續的流程。

2-3 新增「產生郵件 HTML」節點

用「Code」節點來專門生成 HTML 郵件內容。

  1. 在「Loop Over Items」節點的 loop 輸出之後,新增一個「Code」節點,命名為「產生郵件 HTML」。
  2. 在程式碼區塊中,撰寫以下程式碼
const assigneeInfo = $input.item.json;
const meetingInfo = $('提取結構化任務').first().json.meeting_info;
const meetingAttributes = $('提取結構化任務').first().json.meeting_attributes;

const assigneeName = assigneeInfo.name || '夥伴';
const projectName = meetingInfo.project_name || '未指定專案';
const taskCount = assigneeInfo.tasks.length;
const summary = meetingAttributes.summary || '暫無摘要';
const deadlineDate = meetingInfo.deadline_date || '未設定';

let tasksHtml = '';
for (let i = 0; i < assigneeInfo.tasks.length; i++) {
  const task = assigneeInfo.tasks[i];
  const taskNum = i + 1;
  const title = task.title || '未命名任務';
  const desc = task.description || '';
  const timeExpr = task.time_expression || '';
  const priority = task.priority || '';
  
  const priBg = priority === '高' ? '#e74c3c' : '#f39c12';
  const priBadge = priority ? '<span style="background-color: ' + priBg + '; color: white; font-size: 11px; padding: 3px 8px; border-radius: 10px; margin-left: 5px;">' + priority + '優先</span>' : '';
  
  tasksHtml += '<div style="background-color: #fff; border: 2px solid #e3e8ef; border-left: 5px solid #667eea; padding: 18px; margin-bottom: 15px; border-radius: 6px;">';
  tasksHtml += '<div style="margin-bottom: 10px;"><span style="background-color: #667eea; color: white; font-size: 12px; font-weight: bold; padding: 4px 10px; border-radius: 12px;">任務 ' + taskNum + '</span>' + priBadge + '</div>';
  tasksHtml += '<h4 style="margin: 0 0 12px 0; color: #2c3e50; font-size: 16px; font-weight: bold;">' + title + '</h4>';
  tasksHtml += '<p style="margin: 8px 0; color: #666; font-size: 13px;">' + desc + '</p>';
  tasksHtml += '<p style="margin: 12px 0 0 0; color: #555; font-size: 14px;"><strong>📅 預計完成期限:</strong> <span style="color: #e74c3c; font-weight: bold;">' + deadlineDate + '</span>';
  if (timeExpr) {
    tasksHtml += ' <span style="color: #999; font-size: 13px;">(' + timeExpr + ')</span>';
  }
  tasksHtml += '</p></div>';
}

const htmlContent = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: Arial, sans-serif;"><div style="max-width: 600px; margin: 20px auto; background-color: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.15);"><div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 20px; text-align: center;"><h1 style="margin: 0; font-size: 24px; font-weight: bold;">📋 M2A Agent 任務指派通知</h1><p style="margin: 10px 0 0 0; font-size: 14px; opacity: 0.9;">您有新的任務等待處理</p></div><div style="padding: 30px 25px;"><p style="font-size: 16px; color: #333; margin: 0 0 20px 0;">Hi <strong>' + assigneeName + '</strong>,</p><div style="background-color: #f8f9fa; border-left: 4px solid #667eea; padding: 15px; margin-bottom: 25px; border-radius: 4px;"><p style="margin: 0; color: #555; font-size: 14px;">在剛剛的「<strong style="color: #667eea;">' + projectName + '</strong>」會議中,您被指派了 <strong style="color: #e74c3c;">' + taskCount + '</strong> 項新任務,請查收:</p></div><div style="margin-bottom: 30px;">' + tasksHtml + '</div><hr style="border: 0; border-top: 1px solid #e0e0e0; margin: 30px 0;"><div><h3 style="color: #667eea; font-size: 18px; margin: 0 0 15px 0; border-bottom: 2px solid #667eea; padding-bottom: 8px;">📝 相關會議摘要</h3><div style="background-color: #f9f9f9; padding: 15px; border-radius: 6px; border: 1px solid #e8e8e8;"><p style="margin: 0; color: #555; font-size: 14px; line-height: 1.6;">' + summary + '</p></div></div></div><div style="background-color: #f8f9fa; text-align: center; padding: 20px; border-top: 1px solid #e0e0e0;"><p style="margin: 0; font-size: 12px; color: #999;">🤖 這是一封由 M2A Agent 自動發送的通知郵件</p><p style="margin: 8px 0 0 0; font-size: 11px; color: #bbb;">請勿直接回覆此郵件</p></div></div></body></html>';

return {
  email: assigneeInfo.email,
  name: assigneeName,
  projectName: projectName,
  taskCount: taskCount,
  htmlContent: htmlContent
};

這個節點會輸出一個包含 emailprojectNametaskCounthtmlContent 的物件,方便下一個節點取用。

2-4 新增「Gmail」節點

  1. 在「產生郵件 HTML」節點之後,替換 Replace Me,並新增一個 ActiosSend a message 的「Gmail」節點。
  2. 因為有「Loop Over Items」的存在,如果「任務分組」輸出了 2 個人的資料,那麼這個「Gmail」節點就會自動執行 2 次,每一次都只會拿到其中一個人的資料,這樣就可以達成了我們的迴圈寄信的目的。

Step 3:設定「Gmail」節點

這個節點角色非常單純,只負責寄送上一個節點準備好的內容。

3-1 設定郵件收件人、主旨與內容

打開「Gmail」節點,選擇 Send a message 操作,並依序完成以下設定:

  1. Credential: 選擇前面建立的 M2A Gmail 憑證。
  2. To (收件人):切換為表達式模式,填入 {{ $json.email }}
  3. Subject (主旨):切換為表達式模式,填入 【M2A Agent 任務指派】關於「{{ $json.projectName }}」會議,您有 {{ $json.taskCount }} 項新任務
  4. Email Type (郵件類型)HTML
  5. Message (郵件內容):切換為表達式模式,填入 {{ $json.htmlContent }}

3-2 完成迴圈

最後,將「Gmail」節點的輸出端點,連接回「Loop Over Items」節點的左側輸入端點,形成一個完整的閉環迴圈。


Step 4:測試與驗證

完成設定後,需要進行測試。

4-1 執行完整流程測試

  1. 開啟 Gradio 前端介面
  2. 上傳測試音訊檔案
  3. 在使用者指令中輸入:請生成會議摘要與提取行動任務
  4. 點擊「開始處理」

4-2 最終結果

Gmail
Gmail 1
Gmail 2

n8n workflow
WorkFlow


今天的成果總結

完成項目

  • 成功在 Google Cloud 與 n8n 之間建立了完整且正確的 Gmail 專用 OAuth2 憑證
  • 掌握了「職責分離」的設計原則,將 HTML 生成邏輯與單純的郵件寄送動作分。
  • 學會處理一對多通知的技巧,並實作了「分組 → 迴圈 → 生成 → 寄送」的進階工作流程
  • 學會使用「Loop Over Items」節點來建立穩定的自動化迴圈
  • 設計並實作了一個 HTML 郵件,能顯示個人化的任務清單
  • 實現了為每一位會議任務被指派者,全自動發送個人化任務通知信的完整流程

心得

看著 M2A Agent 在會議結束後,它能夠為被指派任務的夥伴寄出精美的任務通知信,這種將複雜流程化為無形、將人工庶務轉化為效率的成就感,正是我追求自動化的初衷。

🎯 明天計劃

新增可以連結到會議紀錄資料庫的連結按鈕,穩定迴圈的穩定性,並做測試驗證。


上一篇
Day 18 整合驗證 — 智慧指派與準確率測試
下一篇
Day 20 郵件優化 — 加入直達按鈕與強化迴圈穩定性
系列文
打造基於 MCP 協議與 n8n 工作流的會議處理 Agent21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言